package com.ld.common.config;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;

import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ToStringSerializer;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.ld.common.config.web.LdGlobal;
import com.ld.common.filter.SecurityFilter;
import com.ld.common.filter.StyleFilter;
import com.ld.common.interceptor.LoginInterceptor;
import com.ld.shieldsb.common.core.collections.ListUtils;
import com.ld.shieldsb.common.core.model.PropertiesModel;
import com.ld.shieldsb.common.core.util.StringUtils;
import com.ld.shieldsb.common.web.filter.XssFilter;

/**
 * web配置类
 * 
 * @ClassName WebConfig
 * @author <a href="mailto:donggongai@126.com" target="_blank">吕凯</a>
 * @date 2018年4月4日 下午12:03:54
 *
 */
@Configuration
@Order(1)
public class WebMainConfig implements WebMvcConfigurer {
    protected String adminPath = LdGlobal.PROJECT_ADMIN_S;
    protected String frontPath = LdGlobal.PROJECT_FRONT_S;
    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    /**
     * 自定义消息转换器，利用fastjson替换掉jackson
     * 
     * @param converters
     */
    @Bean
    public HttpMessageConverters customConverters() {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        // 创建配置类
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullStringAsEmpty);

        // 解决 Long 转json 精度丢失的问题
        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
        serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
        serializeConfig.put(Long.class, ToStringSerializer.instance);
        serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
        fastJsonConfig.setSerializeConfig(serializeConfig);

        // 此处是全局处理方式
        fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
        fastJsonConfig.setCharset(Charset.forName("UTF-8"));
        fastConverter.setFastJsonConfig(fastJsonConfig);

        List<MediaType> supportedMediaTypes = new ArrayList<>();
//        supportedMediaTypes.add(MediaType.ALL);
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        fastConverter.setSupportedMediaTypes(supportedMediaTypes);
        // 支持text 转string
        StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
        return new HttpMessageConverters(fastConverter, stringHttpMessageConverter);
    }

    // 经测试不生效 利用fastjson替换掉jackson，且解决中文乱码问题
//    @Override
//    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
//        FastJsonConfig fastJsonConfig = new FastJsonConfig();
//        fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
//
//        // 解决 Long 转json 精度丢失的问题
//        SerializeConfig serializeConfig = SerializeConfig.globalInstance;
//        serializeConfig.put(BigInteger.class, ToStringSerializer.instance);
//        serializeConfig.put(Long.class, ToStringSerializer.instance);
//        serializeConfig.put(Long.TYPE, ToStringSerializer.instance);
//        fastJsonConfig.setSerializeConfig(serializeConfig);
//
//        // 处理中文乱码问题
//        List<MediaType> fastMediaTypes = new ArrayList<>();
//        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
//        fastConverter.setSupportedMediaTypes(fastMediaTypes);
//        fastConverter.setFastJsonConfig(fastJsonConfig);
//        converters.add(fastConverter);
//    }

    /**
     * sql注入拦截，改为通过durid的wall过滤器处理
     */
//    @Bean
//    public FilterRegistrationBean<AntiSqlInjectionfilter> antiSqlInjectionFilterRegistration() {
//        AntiSqlInjectionfilter filter = new AntiSqlInjectionfilter();
//        filter.setUrlExclusion(Arrays.asList("*.jsp", "*.html", "*.xml", "*/css/*", "*/images/*"));
//        FilterRegistrationBean<AntiSqlInjectionfilter> registration = new FilterRegistrationBean<>(filter);
//        registration.addUrlPatterns("/*");
//        registration.setOrder(1);
//        return registration;
//    }

    /**
     * 时间类型的字段处理
     * 
     * @Title initFields
     * @author 吕凯
     * @date 2019年6月12日 下午3:02:11 void
     */
    @PostConstruct
    public void initFields() {
        ConfigurableWebBindingInitializer initializer = (ConfigurableWebBindingInitializer) handlerAdapter.getWebBindingInitializer();
        if (initializer.getConversionService() != null) {
            GenericConversionService service = (GenericConversionService) initializer.getConversionService();
            service.addConverter(new Converter<String, Date>() {
                private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
                private static final String shortDateFormat = "yyyy-MM-dd";
                private static final String dateFormat2 = "yyyy/MM/dd HH:mm:ss";
                private static final String shortDateFormat2 = "yyyy/MM/dd";

                @Override
                public Date convert(String source) {
                    if (StringUtils.isBlank(source)) {
                        return null;
                    }
                    source = source.trim();
                    try {
                        SimpleDateFormat formatter;
                        if (source.contains("-")) {
                            if (source.contains(":")) {
                                formatter = new SimpleDateFormat(dateFormat);
                            } else {
                                formatter = new SimpleDateFormat(shortDateFormat);
                            }
                            Date dtDate = formatter.parse(source);
                            return dtDate;
                        } else if (source.contains("/")) {
                            if (source.contains(":")) {
                                formatter = new SimpleDateFormat(dateFormat2);
                            } else {
                                formatter = new SimpleDateFormat(shortDateFormat2);
                            }
                            Date dtDate = formatter.parse(source);
                            return dtDate;
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(String.format("parser %s to Date fail(日期转换失败)", source));
                    }

                    throw new RuntimeException(String.format("parser %s to Date fail(日期转换失败)", source));

                }
            });

        }
    }

    /**
     * 安全访问过滤器
     * 
     * @Title xssFilterRegistration
     * @author 吕凯
     * @date 2019年1月14日 下午5:24:59
     * @return FilterRegistrationBean<SecurityFilter>
     */
    @Bean
    public FilterRegistrationBean<SecurityFilter> securityFilterRegistration() {
        SecurityFilter filter = new SecurityFilter();

        List<String> urlExclusions = PropertiesModel.CONFIG.getStringList("security.access.urlExclusions");
        if (ListUtils.isNotEmpty(urlExclusions)) {
            filter.setUrlExclusion(urlExclusions);
        }
        List<String> urlPatterns = PropertiesModel.CONFIG.getStringList("security.access.urlPatterns");
        FilterRegistrationBean<SecurityFilter> registration = null;
        if (ListUtils.isNotEmpty(urlPatterns)) {
            registration = new FilterRegistrationBean<>(filter);
            for (String url : urlPatterns) {
                registration.addUrlPatterns(url);
            }
//            registration.addUrlPatterns("/v2/api-docs");
            registration.setOrder(1);
        }
        return registration;
    }

    /**
     * xssFilter注册
     */
    @Bean
    public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
        XssFilter filter = new XssFilter("content|code|information"); // 不过滤的参数名称（正则）
        filter.setUrlExclusion(Arrays.asList(adminPath + "/notice/update", adminPath + "/notice/add"));
        filter.setRichTextNameRegex("(html\\_.+)|content|information|code|description|demo|nextWeekContent"); // 富文本编辑器不进行处理
        FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>(filter);
        registration.addUrlPatterns("/*");
        registration.setOrder(2);
        return registration;
    }

    /**
     * StyleFilter注册（实际未使用，后期可扩展）
     */
    @Bean
    public FilterRegistrationBean<StyleFilter> styleFilterRegistration() {
        StyleFilter filter = new StyleFilter();
        FilterRegistrationBean<StyleFilter> registration = new FilterRegistrationBean<>(filter);
        registration.addUrlPatterns("/favicon.ico"); // 不带项目名的不走过滤器，无效
//        registration.addUrlPatterns("/css/*");
//        registration.addUrlPatterns("/js/*");
//        registration.addUrlPatterns("/images/*");
        registration.setOrder(3);
        return registration;
    }

    /**
     * 以前要访问一个页面需要先创建个Controller控制类，再写方法跳转到页面 在这里配置后就不需要那么麻烦了，直接访问http://localhost:8080/toLogin就跳转到login.jsp页面了
     * 
     * @param registry
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 默认首页
//        registry.addViewController("/").setViewName("forward:" + adminPath + "/");
        if (StringUtils.isNotEmpty(adminPath) && !adminPath.equals("/")) {
            registry.addViewController("/").setViewName("redirect:" + adminPath + "/");
        }
//        registry.addViewController(adminPath + "/toLogin").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

    /**
     * 拦截器
     * 
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // spring2会拦截静态资源与1.x不同
        // addPathPatterns 用于添加拦截规则
        // excludePathPatterns 用户排除拦截
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").excludePathPatterns(adminPath + "/**/toLogin",
                adminPath + "/**/login", "/**/error", "/**/druid/**",
                // "/**/swagger-resources/**",
                "/**/js/**", "/**/css/**", "/**/font/**", "/**/images/**");
    }

    // 解决跨域问题
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // **代表所有路径
                .allowedOrigins("*") // allowOrigin指可以通过的ip，*代表所有，可以使用指定的ip，多个的话可以用逗号分隔，默认为*
                .allowedMethods("GET", "POST", "HEAD", "PUT", "DELETE") // 指请求方式 默认为*
                .allowCredentials(false) // 支持证书，默认为true
                .maxAge(3600) // 最大过期时间，默认为-1
                .allowedHeaders("*");
    }

    /**
     * 解决ie responsebody返回json的时候提示下载问题
     * 
     * @return
     */
    public MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter() {
        MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList<MediaType>();
        MediaType media = new MediaType(MediaType.TEXT_HTML, Charset.forName("UTF-8"));
        supportedMediaTypes.add(media);
        jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
        return jsonConverter;
    }

    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(customJackson2HttpMessageConverter());
    }
}
